﻿using System;
using System.Collections;
using System.Windows;
using System.Windows.Input;
using System.Windows.Data;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;
using WINSATLib;

namespace Microsoft.WPF.Samples.CoverFlow
{
    /// <summary>
    /// The CoverFlowList is a generic container for the 3d visualization.
    /// </summary>
    /// <remarks>
    /// It is necessary to assign FrontContentTemplate, 
    /// BacksideContentTemplate and set the ItemSource.
    /// </remarks>
    public sealed partial class CoverFlowList
    {
        // fields
        private const int kLongScrollingOffset = 5;
        private const int kAngleToLoadBackside = 110;
        private const double kFacedownAngle = 179.999;
        private const double kScrollStep = 0.2;

        private DoubleAnimation[] _opacityAnimations;
        private DoubleAnimation[] _closingAnimations;
        private DoubleAnimation[] _rotationAnimations;
        private DoubleAnimation[] _translateAnimations;
        private DoubleAnimation _facedownAnimation;
        private CollectionView _itemsView;
        private int _centerItemIndex;
        private int _previousClick;
        private bool _isFacedown;
        private double _internalDuration;
        private DispatcherTimer _scrollTimer;
        private readonly int _doubleClickTime =
            System.Windows.Forms.SystemInformation.DoubleClickTime;
        private Color _myColor;
        private EmissiveMaterial _myEmissiveMaterial;


        // Events

        #region SelectedItemChanged
        private event EventHandler _selectedItemChanged;

        private static readonly object _selectedItemChangedLock =
            new object();

        public event EventHandler SelectedItemChanged
        {
            add
            {
                lock (_selectedItemChangedLock) _selectedItemChanged += value;
            }
            remove
            {
                lock (_selectedItemChangedLock) _selectedItemChanged -= value;
            }
        }
        #endregion


        // Properties
        
        #region AnimationDuration
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty AnimationDurationProperty =
            DependencyProperty.Register(
                "AnimationDuration",
                typeof(double),
                typeof(CoverFlowList),
                new PropertyMetadata(0.15, AnimationDuration_PropertyChanged));

        /// <summary>
        /// Animations the duration_ property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void AnimationDuration_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            ((CoverFlowList)sender).InitAnimations();
        }

        /// <summary>
        /// Gets or sets the duration of the animation.
        /// </summary>
        /// <value>The duration of the animation.</value>
        public double AnimationDuration
        {
            get { return (double)GetValue(AnimationDurationProperty); }
            set { SetValue(AnimationDurationProperty, value); }
        }
        #endregion

        #region BacksideContentTemplate
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty
            BacksideContentTemplateProperty =
                DependencyProperty.Register(
                    "BacksideContentTemplate",
                    typeof(DataTemplate),
                    typeof(CoverFlowList));

        /// <summary>
        /// Gets or sets the backside content template.
        /// </summary>
        /// <value>The backside content template.</value>
        public DataTemplate BacksideContentTemplate
        {
            get
            {
                return (DataTemplate)GetValue(BacksideContentTemplateProperty);
            }
            set { SetValue(BacksideContentTemplateProperty, value); }
        }
        #endregion

        #region FrontContentTemplate
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty FrontContentTemplateProperty =
            DependencyProperty.Register(
                "FrontContentTemplate",
                typeof(DataTemplate),
                typeof(CoverFlowList));

        /// <summary>
        /// Gets or sets the front content template.
        /// </summary>
        /// <value>The front content template.</value>
        public DataTemplate FrontContentTemplate
        {
            get { return (DataTemplate)GetValue(FrontContentTemplateProperty); }
            set { SetValue(FrontContentTemplateProperty, value); }
        }
        #endregion

        #region CenterItemZOffset
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty CenterItemZOffsetProperty =
            DependencyProperty.Register(
                "CenterItemZOffset",
                typeof(double),
                typeof(CoverFlowList),
                new PropertyMetadata(2.8, VisualParameterChanged));

        /// <summary>
        /// Gets or sets the center item Z offset.
        /// </summary>
        /// <value>The center item Z offset.</value>
        public double CenterItemZOffset
        {
            get { return (double)GetValue(CenterItemZOffsetProperty); }
            set { SetValue(CenterItemZOffsetProperty, value); }
        }
        #endregion
 
        #region IsMirrorEnabled
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty IsMirrorEnabledProperty =
            DependencyProperty.Register(
                "IsMirrorEnabled",
                typeof(bool),
                typeof(CoverFlowList),
                new PropertyMetadata(false, IsMirrorEnabled_PropertyChanged));

        /// <summary>
        /// Handles the is mirror enabled property changed event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void IsMirrorEnabled_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            ((CoverFlowList)sender).RegenerateItems();
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is mirror 
        /// enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is mirror enabled; otherwise, 
        /// <c>false</c>.
        /// </value>
        public bool IsMirrorEnabled
        {
            get { return (bool)GetValue(IsMirrorEnabledProperty); }
            set { SetValue(IsMirrorEnabledProperty, value); }
        }
        #endregion

        #region ItemsSource
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register(
                "ItemsSource",
                typeof(IEnumerable),
                typeof(CoverFlowList),
                new PropertyMetadata(
                    new PropertyChangedCallback(ItemsSource_PropertyChanged)));

        /// <summary>
        /// Itemses the source_ property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void ItemsSource_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            var list = sender as CoverFlowList;
            if (list == null) return;

            list._itemsView = null;
            list.SelectedIndex = list.Items.Count > 0 ? 0 : -1;
            if (list.IsLoaded)
            {
                list.RegenerateItems();
            }
        }

        /// <summary>
        /// Gets or sets the items source.
        /// </summary>
        /// <value>The items source.</value>
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
        #endregion

        #region Items
        /// <summary>
        /// Gets the items.
        /// </summary>
        /// <value>The items.</value>
        private CollectionView Items
        {
            get
            {
                return _itemsView 
                    ?? (_itemsView = 
                        new CollectionView(ItemsSource ?? new ArrayList()));
            }
        }
        #endregion

        #region SelectedIndex
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register(
                    "SelectedIndex",
                    typeof(int),
                    typeof(CoverFlowList),
                    new PropertyMetadata(-1, SelectedIndex_PropertyChanged));

        /// <summary>
        /// The index_ property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void SelectedIndex_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            var list = sender as CoverFlowList;

            if (list == null) return;

            if (list.Items.Count <= 0)
            {
                list.SelectedIndex = -1;
                list.SelectedItem = null;
                return;
            }


            if (list.SelectedIndex < 0 ||
                list.SelectedIndex >= list.Items.Count)
            {
                list.SelectedIndex =
                    (int)e.OldValue < list.Items.Count
                        ? (int)e.OldValue
                        : 0;
            }
            else
            {
                if (list._scrollTimer != null)
                {
                    list._scrollTimer.Stop();
                }
                list.SerialMovingToOffset(
                    (int)e.NewValue - list._centerItemIndex);
            }
            var value = (int) e.NewValue;
            if (value > list.Items.Count - 1) value = list.Items.Count - 1;
            list.SelectedItem = list.Items.GetItemAt(value);

        }

        /// <summary>
        /// Gets or sets the index of the selected.
        /// </summary>
        /// <value>The index of the selected.</value>
        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set
            {
                if (value > Items.Count - 1) value = Items.Count - 1;
                SetValue(SelectedIndexProperty, value);
            }
        }
        #endregion

        #region SelectedItem
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register(
                "SelectedItem",
                typeof(object),
                typeof(CoverFlowList),
                new PropertyMetadata(null, SelectedItem_PropertyChanged));

        /// <summary>
        /// Selecteds the item_ property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        private static void SelectedItem_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            var list = sender as CoverFlowList;
            if (list == null) return;
            var index = list.Items.IndexOf(e.NewValue);
            if (index != -1)
            {
                list.SelectedIndex = index;

                return;
            }
            list.SelectedItem =
                list.Items.Contains(e.OldValue) ? e.OldValue : null;

        }

        /// <summary>
        /// Gets or sets the selected item.
        /// </summary>
        /// <value>The selected item.</value>
        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }
        #endregion

        #region SideItemAngle
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SideItemAngleProperty = 
            DependencyProperty.Register(
                "SideItemAngle", 
                typeof(double), 
                typeof(CoverFlowList), 
                new PropertyMetadata(70.0, VisualParameterChanged));
        
        /// <summary>
        /// Gets or sets the side item angle.
        /// </summary>
        /// <value>The side item angle.</value>
        public double SideItemAngle
        {
            get { return (double)GetValue(SideItemAngleProperty); }
            set { SetValue(SideItemAngleProperty, value); }
        }
        #endregion

        #region SideLength
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SideLengthProperty =
            DependencyProperty.Register(
                "SideLength",
                typeof(double),
                typeof(CoverFlowList),
                new PropertyMetadata(6.0, VisualParameterChanged));

        /// <summary>
        /// Gets or sets the length of the side.
        /// </summary>
        /// <value>The length of the side.</value>
        public double SideLength
        {
            get { return (double)GetValue(SideLengthProperty); }
            set { SetValue(SideLengthProperty, value); }
        }
        #endregion

        #region SideItemsCount
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SideItemsCountProperty =
            DependencyProperty.Register(
                "SideItemsCount",
                typeof(int),
                typeof(CoverFlowList),
                new PropertyMetadata(2, SideItemsCount_PropertyChanged));

        /// <summary>
        /// Sides the items count property changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void SideItemsCount_PropertyChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            if ((int)e.NewValue > 0)
            {
                ((CoverFlowList)sender).RegenerateItems();
                return;
            }
            ((CoverFlowList)sender).SideItemsCount = 1;
        }

        /// <summary>
        /// Gets or sets the side items count.
        /// </summary>
        /// <value>The side items count.</value>
        public int SideItemsCount
        {
            get { return (int)GetValue(SideItemsCountProperty); }
            set { SetValue(SideItemsCountProperty, value); }
        }
        #endregion

        #region SidesOffset
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SidesOffsetProperty =
            DependencyProperty.Register(
                "SidesOffset",
                typeof(double),
                typeof(CoverFlowList),
                new PropertyMetadata(0.0, VisualParameterChanged));

        /// <summary>
        /// Gets or sets the sides offset.
        /// </summary>
        /// <value>The sides offset.</value>
        public double SidesOffset
        {
            get { return (double)GetValue(SidesOffsetProperty); }
            set { SetValue(SidesOffsetProperty, value); }
        }
        #endregion

        #region SidesOpacity
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty SidesOpacityProperty =
            DependencyProperty.Register(
                "SidesOpacity",
                typeof(double),
                typeof(CoverFlowList),
                new PropertyMetadata(1.0, VisualParameterChanged));

        /// <summary>
        /// Gets or sets the sides opacity.
        /// </summary>
        /// <value>The sides opacity.</value>
        public double SidesOpacity
        {
            get { return (double)GetValue(SidesOpacityProperty); }
            set { SetValue(SidesOpacityProperty, value); }
        }
        #endregion

        #region ViewDistance
        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ViewDistanceProperty = 
            DependencyProperty.Register(
                "ViewDistance", 
                typeof(double), 
                typeof(CoverFlowList), 
                new PropertyMetadata(106.0));
        
        /// <summary>
        /// Gets or sets the view distance.
        /// </summary>
        /// <value>The view distance.</value>
        public double ViewDistance
        {
            get { return (double)GetValue(ViewDistanceProperty); }
            set { SetValue(ViewDistanceProperty, value); }
        }
        #endregion


        // Constructor

        #region CoverFlowList()
        /// <summary>
        /// Initializes a new instance of the <see cref="CoverFlowList"/> class.
        /// </summary>
        public CoverFlowList()
        {
            InitializeComponent();
            _internalDuration = AnimationDuration;
            try
            {
                IsMirrorEnabled =
                    (new CQueryWinSATClass().Info.SystemRating >= (float)3.0);
            }
// ReSharper disable EmptyGeneralCatchClause
            catch (Exception)
// ReSharper restore EmptyGeneralCatchClause
            {
                // XP does not have system rating...
            }
            // init color options
                    // Define an Emissive Material with a blue brush.
            _myColor = new Color();
            _myColor.ScA = 1;
            _myColor.ScB = 0;
            _myColor.ScR = 0;
            _myColor.ScG = 0;
            _myEmissiveMaterial = new EmissiveMaterial(new SolidColorBrush(_myColor));

        }
        #endregion


        // Methods

        #region OnApplyTemplate()
        /// <summary>
        /// Is invoked whenever application code or internal processes call 
        /// <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            RegenerateItems();
        } 
        #endregion

        #region ScrollTimer_Tick()
        /// <summary>
        /// Handles the Tick event of the ScrollTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance 
        /// containing the event data.</param>
        private void ScrollTimer_Tick(object sender, EventArgs e)
        {
            _scrollTimer.Stop();
            SerialMovingToOffset((int)_scrollTimer.Tag);
        } 
        #endregion

        #region ItemContainer_Remove()
        /// <summary>
        /// Handles the Remove event of the ItemContainer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance 
        /// containing the event data.</param>
        private void ItemContainer_Remove(object sender, EventArgs e)
        {
            var container = sender as ModelUIElement3D;
            if (container == null) return;

            container.RemoveHandler(
                UIElement3D.MouseLeftButtonDownEvent,
                new MouseButtonEventHandler(
                    ItemContainer_MouseLeftButtonDown));
            container.RemoveHandler(
                UIElement3D.MouseWheelEvent,
                new MouseWheelEventHandler(
                    ItemContainer_MouseWheel));

            var diffuseMaterial =
                (DiffuseMaterial)
                ((GeometryModel3D)container.Model).Material;

            var element =
                ((VisualBrush)diffuseMaterial.Brush).Visual
                as FrameworkElement;

            if (element != null) element.DataContext = null;
            _itemsHostModel.Children.Remove(container);
        } 
        #endregion

        #region ItemContainer_MouseLeftButtonDown()
        /// <summary>
        /// Handles the MouseLeftButtonDown event of the ItemContainer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance 
        /// containing the event data.</param>
        private void ItemContainer_MouseLeftButtonDown(
            object sender,
            MouseButtonEventArgs e)
        {
            if (!(e.OriginalSource is UIElement3D)) return;

            var containerIndex =
                _itemsHostModel.Children.IndexOf(e.OriginalSource as Visual3D);

            var offset = SideItemsCount > _centerItemIndex
                ? containerIndex - _centerItemIndex
                : (containerIndex - SideItemsCount);

            if (offset == 0)
            {
                // just custom DoubleClick handling
                if (Environment.TickCount - _previousClick <= _doubleClickTime)
                {
                    RotateSelectedItem();
                }
                _previousClick = Environment.TickCount;
                return;
            }

            SelectedIndex = _centerItemIndex + offset;
            _selectedItemChanged(this, EventArgs.Empty);
        } 
        #endregion

        #region ItemContainer_MouseWheel()
        /// <summary>
        /// Handles the MouseWheel event of the ItemContainer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance 
        /// containing the event data.</param>
        private void ItemContainer_MouseWheel(
            object sender,
            MouseWheelEventArgs e)
        {
            var step = kScrollStep * Math.Sign(e.Delta);
            if (CenterItemZOffset + step >= 0.01 &&
                CenterItemZOffset + step < SideLength)
            {
                CenterItemZOffset += step;
            }
        }
        #endregion

        #region VisualParameterChanged()
        /// <summary>
        /// Visuals the parameter changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The 
        /// <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> 
        /// instance containing the event data.</param>
        static private void VisualParameterChanged(
            DependencyObject sender,
            DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == SideLengthProperty)
            {
                ((CoverFlowList)sender)._camera.Position =
                    new Point3D(0, 0, ((CoverFlowList)sender).SideLength);
            }
            ((CoverFlowList)sender).ApplyControlProperties();
        }
        #endregion

        #region RotateSelectedItem()
        /// <summary>
        /// Rotates the selected item.
        /// </summary>
        public void RotateSelectedItem()
        {
            if (Items.Count <= 0 ||
                SelectedIndex < 0) return;

            var container =
                _itemsHostModel.Children
                    [Math.Min(SelectedIndex, SideItemsCount)]
                        as ModelUIElement3D;

            PrepareBackMaterial(container, Items.GetItemAt(SelectedIndex));

            if (container == null) return;

            var tg = (Transform3DGroup) container.Transform;

            var rotationTransform =
                ((RotateTransform3D)(tg).Children[0]).Rotation
                    as AxisAngleRotation3D;

            if (_facedownAnimation == null)
            {
                _facedownAnimation =
                    new DoubleAnimation(
                        0,
                        new Duration(TimeSpan.FromSeconds(AnimationDuration)));
            }
            var ts = _facedownAnimation.Duration.TimeSpan.TotalSeconds;
            if (Math.Abs(ts - AnimationDuration) > 0.001)
            {
                _facedownAnimation.Duration =
                    new Duration(TimeSpan.FromSeconds(AnimationDuration));
            }
            _facedownAnimation.To = _isFacedown ? 0 : kFacedownAngle;
            _isFacedown = !_isFacedown;
            if (rotationTransform != null)
            {
                rotationTransform.BeginAnimation(
                    AxisAngleRotation3D.AngleProperty,
                    _facedownAnimation);
            }
        }
        #endregion

        #region RegenerateItems()
        /// <summary>
        /// Regenerates the items.
        /// </summary>
        public void RegenerateItems()
        {
            _itemsHostModel.Children.Clear();
            InitAnimations();
            for (var i = Math.Max(0, SelectedIndex - SideItemsCount);
                i < Math.Min(SelectedIndex + SideItemsCount + 1, Items.Count);
                i++)
            {
                var container = CreateContainerFrontOnly(Items.GetItemAt(i));
                PrepareItemsContainerTransform(
                    container,
                    i - SelectedIndex,
                    true);
                _itemsHostModel.Children.Add(container);
            }
            _centerItemIndex = SelectedIndex;
            GC.Collect();
        }
        #endregion

        #region ApplyControlProperties()
        /// <summary>
        /// Applies the control properties.
        /// </summary>
        private void ApplyControlProperties()
        {
            for (var i = 0; i < _itemsHostModel.Children.Count; i++)
            {
                var offset =
                    _centerItemIndex >= SideItemsCount
                        ? i - SideItemsCount
                        : i - _centerItemIndex;

                PrepareItemsContainerTransform(
                    _itemsHostModel.Children[i] as ModelUIElement3D,
                    offset,
                    true);
            }
        }
        #endregion

        #region PrepareItemsContainerTransform()
        /// <summary>
        /// Prepares the items container transform.
        /// </summary>
        /// <param name="itemsContainer">The items container.</param>
        /// <param name="offsetFromCenter">The offset from center.</param>
        /// <param name="usingAnimation">if set to <c>true</c> using animation.
        /// </param>
        private void PrepareItemsContainerTransform(
            ModelUIElement3D itemsContainer,
            int offsetFromCenter,
            bool usingAnimation)
        {
            double itemAngle = 0;
            double xOffset = 0;
            var opacity = 1.0;
            if (offsetFromCenter != 0 &&
                Math.Abs(offsetFromCenter) <= SideItemsCount)
            {
                // calculating item angle
                itemAngle = -SideItemAngle * Math.Sign(offsetFromCenter);

                // calculating item translation offset
                xOffset =
                    SidesOffset *
                    Math.Sign(offsetFromCenter) +
                    (SideLength - 1) /
                    SideItemsCount *
                    offsetFromCenter +
                    Math.Sign(offsetFromCenter);

                // calculating opacity
                opacity =
                    1 -
                    ((1 - SidesOpacity) / SideItemsCount) *
                    Math.Abs(offsetFromCenter);
            }
            else if (Math.Abs(offsetFromCenter) > SideItemsCount)
            {
                Dispatcher.BeginInvoke(
                    new EventHandler(ItemContainer_Remove),
                    DispatcherPriority.Send,
                    itemsContainer,
                    null);
                return;
            }

            var transCollection =
                ((Transform3DGroup)itemsContainer.Transform).Children;

            var diffuseMaterial =
                    ((DiffuseMaterial)
                        ((GeometryModel3D)itemsContainer.Model).Material);

            var firstElement = (RotateTransform3D)transCollection[0];

            var firstAxisAngleRotation3D =
                (AxisAngleRotation3D)firstElement.Rotation;

            var secondElement = transCollection[1];

            var secondTranslateTransform3D =
                (TranslateTransform3D)secondElement;

            if (usingAnimation)
            {
                var animationIndex = SideItemsCount + offsetFromCenter;

                // translation animation
                if (secondTranslateTransform3D.OffsetX != xOffset)
                {
                    PrepareAnimationAtIndex(
                        _translateAnimations,
                        animationIndex);
                    _translateAnimations[animationIndex].To = xOffset;
                    secondElement.BeginAnimation(
                        TranslateTransform3D.OffsetXProperty,
                        _translateAnimations[animationIndex]);
                }

                // rotation animation
                if (itemAngle != firstAxisAngleRotation3D.Angle &&
                    (offsetFromCenter != 0 || !_isFacedown))
                {
                    PrepareAnimationAtIndex(_rotationAnimations, animationIndex);
                    _rotationAnimations[animationIndex].To = itemAngle;
                    firstElement.Rotation.BeginAnimation(
                        AxisAngleRotation3D.AngleProperty,
                        _rotationAnimations[animationIndex]);
                    if (Math.Abs(itemAngle) > kAngleToLoadBackside)
                    {
                        PrepareBackMaterial(
                            itemsContainer,
                            Items.GetItemAt(
                                _centerItemIndex + offsetFromCenter));
                    }
                }

                // closing animation
                if (offsetFromCenter == 0 &&
                    secondTranslateTransform3D.OffsetZ != CenterItemZOffset)
                {
                    _closingAnimations[0].To = CenterItemZOffset;
                    secondElement.BeginAnimation(
                        TranslateTransform3D.OffsetZProperty,
                        _closingAnimations[0]);
                }
                else if (secondTranslateTransform3D.OffsetZ != 0 &&
                    offsetFromCenter != 0)
                {
                    secondElement.BeginAnimation(
                        TranslateTransform3D.OffsetZProperty,
                        _closingAnimations[1]);
                }



                // opacity animation
                if (opacity != diffuseMaterial.Brush.Opacity)
                {
                    PrepareAnimationAtIndex(_opacityAnimations, animationIndex);
                    _opacityAnimations[animationIndex].To = opacity;
                    diffuseMaterial.Brush.BeginAnimation(
                        Brush.OpacityProperty,
                        _opacityAnimations[animationIndex]);
                }
            }
            else
            {
                // applying parameters without animation
                firstAxisAngleRotation3D.Angle = itemAngle;
                secondTranslateTransform3D.OffsetZ =
                    offsetFromCenter == 0 ? CenterItemZOffset : 0;
                secondTranslateTransform3D.OffsetX = xOffset;
                diffuseMaterial.Brush.Opacity = opacity;
            }
        }
        #endregion

        #region PrepareAnimationAtIndex()
        /// <summary>
        /// Prepares the index of the animation at.
        /// </summary>
        /// <param name="animations">The animations.</param>
        /// <param name="index">The index.</param>
        private void PrepareAnimationAtIndex(
            DoubleAnimation[] animations,
            int index)
        {
            if (animations[index] == null)
            {
                animations[index] =
                    new DoubleAnimation
                            {
                                To = 0,
                                Duration =
                                    new Duration(
                                        TimeSpan.FromSeconds(
                                            _internalDuration))
                            };
                _scrollTimer.Interval = TimeSpan.FromSeconds(_internalDuration);
                return;

            }
            var ts = animations[index].Duration.TimeSpan.TotalSeconds;

            if (Math.Abs(ts - _internalDuration) <= 0.00001) return;

            animations[index].Duration =
                new Duration(TimeSpan.FromSeconds(_internalDuration));

            _scrollTimer.Interval = TimeSpan.FromSeconds(_internalDuration);
        }
        #endregion

        #region SerialMovingToOffset()
        /// <summary>
        /// This funktion will create new containers for elements, that are not in the visual 3d scene and move every existing Item.
        /// </summary>
        /// <param name="offset">The offset.</param>
        private void SerialMovingToOffset(int offset)
        {
            if (offset == 0) return;

            _isFacedown = false;
            _internalDuration =
                Math.Abs(offset) >= kLongScrollingOffset
                    ? 0.1
                    : AnimationDuration;

            // Adding new side item
            var offsideIndex =
                _centerItemIndex + Math.Sign(offset) * (SideItemsCount + 1);

            if (offsideIndex >= 0 && offsideIndex < Items.Count)
            {
                var risingContainer =
                    CreateContainerFrontOnly(Items.GetItemAt(offsideIndex));
                var tfg = ((Transform3DGroup)risingContainer.Transform);
                ((TranslateTransform3D)tfg.Children[1]).OffsetX =
                    SidesOffset *
                    Math.Sign(offset) +
                    (SideLength - 1) /
                    SideItemsCount *
                    (SideItemsCount + 1) *
                    Math.Sign(offset) +
                    Math.Sign(offset);

                _itemsHostModel.Children.Insert(
                    (offset > 0
                        ? _itemsHostModel.Children.Count
                        : 0),
                    risingContainer);
            }

            // Move every existing items
            for (var i = 0; i < _itemsHostModel.Children.Count; i++)
            {
                var newOffset =
                    i -
                    Math.Sign(offset) -
                    (SideItemsCount >= _centerItemIndex
                        ? _centerItemIndex
                        : (SideItemsCount + (offset < 0 ? 1 : 0)));

                PrepareItemsContainerTransform(
                    _itemsHostModel.Children[i] as ModelUIElement3D,
                    newOffset,
                    true);
            }

            _centerItemIndex += Math.Sign(offset);
            offset -= Math.Sign(offset);
            if (offset != 0)
            {
                _scrollTimer.Tag = offset;
                _scrollTimer.Start();
                return;
            }

            _centerItemIndex = SelectedIndex;
        }
        #endregion

        #region GenerateVisualBrush()
        /// <summary>
        /// Generates the visual brush.
        /// </summary>
        /// <param name="brushElement">The brush element.</param>
        /// <param name="needMirror">if set to <c>true</c> [need mirror].</param>
        /// <returns></returns>
        private VisualBrush GenerateVisualBrush(
            UIElement brushElement,
            bool needMirror)
        {
            var materialBrush = new VisualBrush();
            RenderOptions.SetCacheInvalidationThresholdMinimum(
                materialBrush, 
                0);

            RenderOptions.SetCachingHint(materialBrush, CachingHint.Cache);
            if (needMirror)
            {
                // creating mirrors
                var mirrorTemplate =
                    (DataTemplate)Resources["MirrorBrushTemplate"];

                var hostBorder =
                    ((Panel)mirrorTemplate.LoadContent()).Children[0] as Border;

                if (hostBorder != null)
                {
                    hostBorder.Child = brushElement;
                    materialBrush.Visual = hostBorder.Parent as Visual;
                }
                return materialBrush;
            }

            materialBrush.Visual = brushElement;
            if (materialBrush.CanFreeze)
            {
                materialBrush = materialBrush.GetAsFrozen() as VisualBrush;
            }
            return materialBrush;
        }
        #endregion

        #region CreateContainerFrontOnly()
        /// <summary>
        /// Creates the container front only.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private ModelUIElement3D CreateContainerFrontOnly(object item)
        {
            var container = new ModelUIElement3D();

            // Creating transforms
            var tfg = new Transform3DGroup();
            container.Transform = tfg;

            tfg.Children.Add(new RotateTransform3D(new AxisAngleRotation3D()));
            tfg.Children.Add(new TranslateTransform3D());

            // Subscribeng for events
            container.AddHandler(
                UIElement3D.MouseLeftButtonDownEvent,
                new MouseButtonEventHandler(ItemContainer_MouseLeftButtonDown));
            container.AddHandler(
                UIElement3D.MouseWheelEvent,
                new MouseWheelEventHandler(ItemContainer_MouseWheel));

            // setting geometry & material
            container.Model =
                new GeometryModel3D(
                    Resources[IsMirrorEnabled
                        ? "ItemWithMirrorMeshGeometry3D"
                        : "ItemMeshGeometry3D"] as MeshGeometry3D,
                    new DiffuseMaterial());

            if (FrontContentTemplate != null)
            {
                var root =
                    FrontContentTemplate.LoadContent() as FrameworkElement;

                if (root != null)
                {
                    // Give our FrontContentTemplate the Person to be visualizised
                    root.DataContext = item;

                    var diffuseMaterial =
                        (DiffuseMaterial)
                            (container.Model as GeometryModel3D).Material;

                    diffuseMaterial.Brush =
                        GenerateVisualBrush(root, IsMirrorEnabled);
                }
            }
            return container;
        }
        #endregion

        #region PrepareBackMaterial()
        /// <summary>
        /// Prepares the back material.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="item">The item.</param>
        private void PrepareBackMaterial(
            ModelUIElement3D container, 
            object item)
        {
            if (BacksideContentTemplate == null ||
                ((GeometryModel3D)container.Model).BackMaterial != null)
            {
                return;
            }

            var root =
                BacksideContentTemplate.LoadContent() as FrameworkElement;

            if (root == null) return;

            root.DataContext = item;
            root.LayoutTransform = new ScaleTransform(-1, 1); // flip by X axis
            ((GeometryModel3D)container.Model).BackMaterial =
                new DiffuseMaterial(GenerateVisualBrush(root, IsMirrorEnabled));
        }
        #endregion

        #region InitAnimations()
        /// <summary>
        /// Inits the animations.
        /// </summary>
        private void InitAnimations()
        {
            if (_scrollTimer == null)
            {
                _scrollTimer =
                    new DispatcherTimer(
                        TimeSpan.FromSeconds(AnimationDuration),
                        DispatcherPriority.Normal,
                        ScrollTimer_Tick,
                        Dispatcher);
                _scrollTimer.Stop();
            }

            var animationsCount = SideItemsCount * 2 + 1;
            _rotationAnimations = new DoubleAnimation[animationsCount];
            _translateAnimations = new DoubleAnimation[animationsCount];
            _opacityAnimations = new DoubleAnimation[animationsCount];

            // closing animation init
            _closingAnimations = new DoubleAnimation[2];

            _closingAnimations[0] =
                new DoubleAnimation(
                    CenterItemZOffset,
                    new Duration(
                        TimeSpan.FromSeconds(AnimationDuration)))
                    {
                        DecelerationRatio = 1
                    };

            _closingAnimations[1] =
                new DoubleAnimation(
                    0,
                    new Duration(
                        TimeSpan.FromSeconds(AnimationDuration)))
                    {
                        DecelerationRatio = 1
                    };

            GC.Collect();
        }
        #endregion
    }
}

